/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using DaveSexton.DocProject.Resources;

namespace DaveSexton.DocProject.Wizards.Foundation
{
	/// <remarks>
	/// <para>It's important to note that this class does not check for circular references, which will cause infinite loops when querying certain properties or calling certain methods.</para>
	/// <para>Disposing the controller disposes of all of the wizard steps in the <see cref="FirstStep" /> chain and the <see cref="FirstStepLastGroup" /> chain.</para>
	/// <para>This class is not thread-safe.</para>
	/// </remarks>
	public sealed class WizardController : Component
	{
		#region Public Properties
		public WizardStep FirstStep
		{
			get
			{
				return firstStep.FirstStep;
			}
		}

		public WizardStep FirstStepLastGroup
		{
			get
			{
				// in case steps are injected before the last step that is referenced, use the FirstStep property
				return (firstStepLastGroup != null) ? firstStepLastGroup.FirstStep : null;
			}
		}

		public WizardStep FirstVisibleStep
		{
			get
			{
				return FirstVisibleStepInFirstGroup ?? FirstVisibleStepInLastGroup;
			}
		}

		public WizardStep FirstVisibleStepInFirstGroup
		{
			get
			{
				return firstStep.FirstVisibleStep;
			}
		}

		public WizardStep FirstVisibleStepInLastGroup
		{
			get
			{
				return (firstStepLastGroup != null) ? firstStepLastGroup.FirstVisibleStep : null;
			}
		}

		public WizardStep CurrentStep
		{
			get
			{
				if (!started)
					throw new InvalidOperationException(Errors.WizardNotStarted);

				return currentStep;
			}
			private set
			{
				if (currentStep != null)
					currentStep.StateChanged -= currentStep_StateChanged;

				currentStep = value;

				if (currentStep != null)
					currentStep.StateChanged += currentStep_StateChanged;
			}
		}

		public SettingsDictionary<string, object> Data
		{
			get
			{
				return data;
			}
		}

		public bool InLastGroup
		{
			get
			{
				if (!started)
					throw new InvalidOperationException(Errors.WizardNotStarted);

				return inLastGroup;
			}
		}

		public bool IsCurrentStepLast
		{
			get
			{
				if (!started)
					throw new InvalidOperationException(Errors.WizardNotStarted);

				// - read the fields, not their properties because some of the properties read FirstStep.
				// - compare to null when there is a last step but the current step isn't in the last 
				//   group so that false is returned.
				return currentStep == ((inLastGroup) ? firstStepLastGroup.LastStep :
					(firstStepLastGroup != null) ? null : firstStep.LastStep);
			}
		}

		public bool IsCurrentLastVisibleStep
		{
			get
			{
				if (!started)
					throw new InvalidOperationException(Errors.WizardNotStarted);

				// - read the fields, not their properties because some of the properties read FirstStep.
				if (inLastGroup)
					return currentStep == firstStepLastGroup.LastVisibleStep;
				else if (firstStepLastGroup != null && (!currentStep.Visible || firstStepLastGroup.FirstVisibleStep != null))
					return false;
				else
					return currentStep == firstStep.LastVisibleStep;
			}
		}

		public bool IsCurrentFirstVisibleStep
		{
			get
			{
				if (!started)
					throw new InvalidOperationException(Errors.WizardNotStarted);

				// use FirstStep property, not its field
				return currentStep == FirstVisibleStep;
			}
		}

		public bool IsCurrentStepFirst
		{
			get
			{
				if (!started)
					throw new InvalidOperationException(Errors.WizardNotStarted);

				// use FirstStep property, not its field
				return currentStep == FirstStep;
			}
		}

		public object Owner
		{
			get
			{
				return owner;
			}
		}

		public bool Started
		{
			get
			{
				return started;
			}
		}

		/// <summary>
		/// Gets whether the current wizard step has requested to supress the default navigation. 
		/// </summary>
		/// <remarks>
		/// This property can be used so that multi-line input controls presented to the user will 
		/// accept the enter key instead of the <see cref="WizardForm"/>'s default 
		/// <see cref="Form.AcceptButton"/>
		/// </remarks>
		public bool SuppressDefaultNavigation
		{
			get
			{
				if (!started)
					throw new InvalidOperationException(Errors.WizardNotStarted);

				return currentStep != null && currentStep.SuppressDefaultNavigation;
			}
		}

		public object CurrentStepUserInterface
		{
			get
			{
				if (!started)
					throw new InvalidOperationException(Errors.WizardNotStarted);

				return currentStep.GetUserInterfaceInternal(this, false);
			}
		}

		public bool PreloadUserInterfacesOnStart
		{
			get
			{
				return preloadUserInterfacesOnStart;
			}
			set
			{
				preloadUserInterfacesOnStart = value;
			}
		}
		#endregion

		#region Private / Protected
		private readonly WizardStep firstStep;
		private readonly WizardStep firstStepLastGroup;
		private readonly SettingsDictionary<string, object> data = new SettingsDictionary<string, object>(
			System.Globalization.CultureInfo.InvariantCulture, StringComparer.OrdinalIgnoreCase);
		private readonly object owner;
		private WizardStep currentStep;
		private bool started, inLastGroup, preloadUserInterfacesOnStart = true;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="WizardController" /> class.
		/// </summary>
		public WizardController(WizardStep firstStep)
		{
			if (firstStep == null)
				throw new ArgumentNullException("firstStep");

			this.firstStep = firstStep;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="WizardController" /> class.
		/// </summary>
		public WizardController(WizardStep firstStep, WizardStep firstStepLastGroup)
			: this(firstStep)
		{
			this.firstStepLastGroup = firstStepLastGroup;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="WizardController" /> class.
		/// </summary>
		public WizardController(object owner, WizardStep firstStep)
			: this(firstStep)
		{
			this.owner = owner;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="WizardController" /> class.
		/// </summary>
		public WizardController(object owner, WizardStep firstStep, WizardStep firstStepLastGroup)
			: this(firstStep, firstStepLastGroup)
		{
			this.owner = owner;
		}
		#endregion

		#region Methods
		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand,
			Flags = System.Security.Permissions.SecurityPermissionFlag.Infrastructure)]
		public override object InitializeLifetimeService()
		{
			return null;
		}

		public void Start(bool moveToVisible)
		{
			if (started)
				throw new InvalidOperationException(Errors.WizardAlreadyStarted);

			data.Clear();

			// set CurrentStep property, not its field
			// use FirstStep property, not its field
			CurrentStep = (moveToVisible) ? FirstVisibleStep : FirstStep;

			if (currentStep == null)
				throw new InvalidOperationException(Errors.WizardNoVisibleSteps);

			if (preloadUserInterfacesOnStart)
			{
				ForEachStep(delegate(WizardStep step)
				{
					if (step.CanPreloadUserInterface && !step.uiLoaded)
						step.GetUserInterfaceInternal(this, true);
				});
			}

			started = true;
		}

		public bool CanMoveNext(bool visibleOnly)
		{
			if (!started)
				throw new InvalidOperationException(Errors.WizardNotStarted);

			WizardStep nextStep;
			bool groupChange;
			return CanMoveNext(true, visibleOnly, out nextStep, out groupChange);
		}

		private bool CanMoveNext(bool checkIfLast, bool visibleOnly, out WizardStep nextStep, out bool groupChange)
		{
			nextStep = GetNextStep(checkIfLast, visibleOnly, out groupChange);

			return nextStep != null && currentStep.CanNavigateAwayInternal(this, nextStep, true) && nextStep.CanNavigateToInternal(this, currentStep, true);
		}

		public void MoveNext(bool visibleOnly)
		{
			if (!started)
				throw new InvalidOperationException(Errors.WizardNotStarted);

			WizardStep nextStep;
			bool groupChange;

			if (!CanMoveNext(false, visibleOnly, out nextStep, out groupChange))
				throw new InvalidOperationException(Errors.WizardCannotMoveNext);

			if (groupChange)
				inLastGroup = true;

			WizardStep lastStep = currentStep;
			CurrentStep = nextStep;		// set CurrentStep property, not its field

			lastStep.NavigatedAwayInternal(this, nextStep, true);
			nextStep.NavigatedToInternal(this, lastStep, true);
		}

		public bool CanMovePrevious(bool visibleOnly)
		{
			if (!started)
				throw new InvalidOperationException(Errors.WizardNotStarted);

			WizardStep previousStep;
			bool groupChange;
			return CanMovePrevious(true, visibleOnly, out previousStep, out groupChange);
		}

		private bool CanMovePrevious(bool checkIfFirst, bool visibleOnly, out WizardStep previousStep, out bool groupChange)
		{
			previousStep = GetPreviousStep(checkIfFirst, visibleOnly, out groupChange);

			return previousStep != null && currentStep.CanNavigateAwayInternal(this, previousStep, false) && previousStep.CanNavigateToInternal(this, currentStep, false);
		}

		public void MovePrevious(bool visibleOnly)
		{
			if (!started)
				throw new InvalidOperationException(Errors.WizardNotStarted);

			WizardStep previousStep;
			bool groupChange;

			if (!CanMovePrevious(false, visibleOnly, out previousStep, out groupChange))
				throw new InvalidOperationException(Errors.WizardCannotMovePrevious);

			if (groupChange)
				inLastGroup = false;

			WizardStep lastStep = currentStep;
			CurrentStep = previousStep;		// set CurrentStep property, not its field

			lastStep.NavigatedAwayInternal(this, previousStep, false);
			previousStep.NavigatedToInternal(this, lastStep, false);
		}

		public bool CanFinishWizard()
		{
			if (!started)
				throw new InvalidOperationException(Errors.WizardNotStarted);

			bool canFinish = true;

			ForEachStep(delegate(WizardStep step)
			{
				if (!step.CanFinishWizardInternal())
				{
					canFinish = false;
					return false;
				}

				return true;
			});

			return canFinish;
		}

		public bool CanCancelWizard()
		{
			if (!started)
				throw new InvalidOperationException(Errors.WizardNotStarted);

			bool canCancel = true, afterCurrentStep = false;

			ForEachStep(delegate(WizardStep step)
			{
				if (!afterCurrentStep)
				{
					if (step == currentStep)
						// only the current step and subsequent steps may cancel the wizard
						afterCurrentStep = true;
					else
						return true;		// continue
				}

				if (!step.CanCancelWizardInternal())
				{
					canCancel = false;
					return false;
				}

				return true;		// continue
			});

			return canCancel;
		}

		public void Finish(bool canceled)
		{
			if (!started)
				throw new InvalidOperationException(Errors.WizardNotStarted);

			if (canceled)
			{
				if (!CanCancelWizard())
					throw new InvalidOperationException(Errors.WizardCannotBeCanceled);
			}
			else if (!CanFinishWizard())
				throw new InvalidOperationException(Errors.WizardCannotBeFinished);

			ForEachStep(delegate(WizardStep step)
			{
				if (canceled)
					step.WizardCanceledInternal(this);
				else
					step.WizardFinishedInternal(this);
			});

			// NOTE: do not clear data field so that it can be read after the wizard has finished; it's cleared in the Start method
			
			inLastGroup = false;
			started = false;
			CurrentStep = null;		// set CurrentStep property, not its field
		}

		public WizardStep GetNextStep(bool visibleOnly)
		{
			if (!started)
				throw new InvalidOperationException(Errors.WizardNotStarted);

			bool groupChange;
			return GetNextStep(false, visibleOnly, out groupChange);
		}

		private WizardStep GetNextStep(bool returnNullIfLast, bool visibleOnly, out bool groupChange)
		{
			groupChange = false;

			WizardStep nextStep = (visibleOnly) ? currentStep.NextVisibleStep : currentStep.NextStep;

			if (nextStep == null)
			{
				if (!inLastGroup && firstStepLastGroup != null)
				{
					// read FirstStepLastGroup property, not its field
					nextStep = (visibleOnly) ? FirstVisibleStepInLastGroup : FirstStepLastGroup;

					if (nextStep == null)
					{
						if (returnNullIfLast)
							return null;
						else
							goto error;
					}

					groupChange = true;
				}
				else if (returnNullIfLast)
					return null;
				else
					goto error;
			}

			return nextStep;

		error:
			if (visibleOnly)
				throw new InvalidOperationException(Errors.WizardFinalVisibleStep);
			else
				throw new InvalidOperationException(Errors.WizardFinalStep);
		}

		public WizardStep GetPreviousStep(bool visibleOnly)
		{
			if (!started)
				throw new InvalidOperationException(Errors.WizardNotStarted);

			bool groupChange;
			return GetPreviousStep(false, visibleOnly, out groupChange);
		}

		private WizardStep GetPreviousStep(bool returnNullIfFirst, bool visibleOnly, out bool groupChange)
		{
			groupChange = false;

			WizardStep previousStep = (visibleOnly) ? currentStep.PreviousVisibleStep : currentStep.PreviousStep;

			if (previousStep == null)
			{
				if (inLastGroup)
				{
					// read FirstStep property, not its field
					previousStep = (visibleOnly) ? FirstStep.LastVisibleStep : FirstStep.LastStep;

					if (previousStep == null)
					{
						if (returnNullIfFirst)
							return null;
						else
							goto error;
					}

					groupChange = true;
				}
				else if (returnNullIfFirst)
					return null;
				else
					goto error;
			}

			return previousStep;

		error:
			if (visibleOnly)
				throw new InvalidOperationException(Errors.WizardFirstVisibleStep);
			else
				throw new InvalidOperationException(Errors.WizardFirstStep);
		}

		/// <summary>
		/// Iterates through each step in the order they will be presented to the user.
		/// </summary>
		public void ForEachStep(Action<WizardStep> action)
		{
			foreach (WizardStep step in FirstStep)
				action(step);

			if (firstStepLastGroup != null)
				foreach (WizardStep step in FirstStepLastGroup)
					action(step);
		}

		/// <summary>
		/// Iterates through each step in the order they will be presented to the user and allows the delegate to cancel the operation by returning <b>false</b>.
		/// </summary>
		public void ForEachStep(Predicate<WizardStep> stopOnFalse)
		{
			foreach (WizardStep step in FirstStep)
				if (!stopOnFalse(step))
					return;

			if (firstStepLastGroup != null)
				foreach (WizardStep step in FirstStepLastGroup)
					if (!stopOnFalse(step))
						return;
		}

		/// <summary>
		/// Releases the unmanaged resources used by an instance of the <see cref="WizardController" /> class and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
		protected override void Dispose(bool disposing)
		{
			try
			{
				if (disposing)
				{
					Exception lastError = null;

					// read FirstStep property, not its field
					lastError = DisposeAllSteps(FirstStep);

					if (firstStepLastGroup != null)
						lastError = DisposeAllSteps(FirstStepLastGroup);	// read FirstStepLastGroup property

					if (lastError != null)
						throw lastError;
				}
			}
			finally
			{
				base.Dispose(disposing);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private static Exception DisposeAllSteps(WizardStep step)
		{
			Exception lastError = null;

			while (step != null)
			{
				try
				{
					step.Dispose();
				}
				catch (Exception ex)
				{
					lastError = ex;
				}

				step = step.NextStep;
			}

			return lastError;
		}
		#endregion

		#region Events
		private readonly object CurrentStepStateChangedEventLock = new object();
		private EventHandler<WizardStepStateChangedEventArgs> CurrentStepStateChangedEvent;

		/// <summary>
		/// Event raised when the <see cref="CurrentStep" />'s <see cref="WizardStep.StateChanged" /> event is raised.
		/// </summary>
		public event EventHandler<WizardStepStateChangedEventArgs> CurrentStepStateChanged
		{
			add
			{
				lock (CurrentStepStateChangedEventLock)
				{
					CurrentStepStateChangedEvent += value;
				}
			}
			remove
			{
				lock (CurrentStepStateChangedEventLock)
				{
					CurrentStepStateChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="CurrentStepStateChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="WizardStepStateChangedEventArgs" /> object that provides the arguments for the event.</param>
		private void OnCurrentStepStateChanged(WizardStepStateChangedEventArgs e)
		{
			EventHandler<WizardStepStateChangedEventArgs> handler = null;

			lock (CurrentStepStateChangedEventLock)
			{
				handler = CurrentStepStateChangedEvent;
			}

      if (handler != null)
			  handler(this, e);
		}
		#endregion

		#region Event Handlers
		private void currentStep_StateChanged(object sender, WizardStepStateChangedEventArgs e)
		{
			OnCurrentStepStateChanged(e);
		}
		#endregion
	}
}
